﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Text;
using xinhua302;

public class Anim : MonoBehaviour
{
    private List<PosNormalTexTanSkinned> vertices;
    private List<int> indices;
    private List<Subset> subsets;
    private List<Material> mats;
    private SkinnedData skinnedData;

    private MeshFilter meshFilter;
    private MeshRenderer meshRender;

    private void Start()
    {
        meshFilter = GetComponent<MeshFilter>();
        meshRender = GetComponent<MeshRenderer>();
        LoadM3d("soldier.m3d");
        GenerateMesh();
        ApplyMaterials();
    }

    private void Update()
    {
        if (skinnedData == null)
        {
            return;
        }

        string clipName = "Take1";
        float startTime = skinnedData.GetClipStartTime(clipName);
        float endTime = skinnedData.GetClipEndTime(clipName);

        float t = Time.realtimeSinceStartup % (endTime - startTime);

        List<Matrix4x4> finalTransforms = skinnedData.GetFinalTransforms(clipName, t);

        Mesh mesh = meshFilter.sharedMesh;
        List<Vector3> inVertices = new List<Vector3>();
        List<Vector3> inNormals = new List<Vector3>();
        List<Vector4> inTangent = new List<Vector4>();

        for (int index = 0; index < subsets.Count; index++)
        {
            Subset subset = subsets[index];
            for (int i = 0; i < subset.VertexCount; i++)
            {
                PosNormalTexTanSkinned pos = vertices[subset.VertexStart + i];
                float[] weights = new float[4];
                weights[0] = pos.Weights.x;
                weights[1] = pos.Weights.y;
                weights[2] = pos.Weights.z;
                weights[3] = 1 - pos.Weights.x - pos.Weights.y - pos.Weights.z;

                Vector3 inP = Vector3.zero;
                Vector3 inN = Vector3.zero;
                Vector3 inT = Vector3.zero;

                for (int j = 0; j < 4; j++)
                {
                    byte boneIndex = pos.BoneIndices[j];
                    Matrix4x4 m = finalTransforms[boneIndex];

                    inP += m.MultiplyPoint3x4(pos.Pos) * weights[j];
                    inN += m.MultiplyVector(pos.Normal) * weights[j];
                    inT += m.MultiplyVector(pos.TangentU) * weights[j];
                }

                inN.Normalize();
                inT.Normalize();
                Vector4 inTan = new Vector4(inT.x, inT.y, inT.z, pos.TangentU.w);

                inVertices.Add(inP);
                inNormals.Add(inN);
                inTangent.Add(inTan);
            }

        }
        mesh.SetVertices(inVertices);
        mesh.SetNormals(inNormals);
        mesh.SetTangents(inTangent);

    }

    private void ApplyMaterials()
    {
        meshRender.materials = mats.ToArray();
    }

    private void GenerateMesh()
    {
        Mesh mesh = new Mesh();
        mesh.name = "soldier";
        mesh.subMeshCount = subsets.Count;

        List<Vector3> inVertices = new List<Vector3>();
        List<Vector3> inNormals = new List<Vector3>();
        List<Vector4> inTangent = new List<Vector4>();
        List<Vector2> inUV = new List<Vector2>();

        for (int index = 0; index < subsets.Count; index++)
        {
            Subset subset = subsets[index];
            for (int i = 0; i < subset.VertexCount; i++)
            {
                inVertices.Add(vertices[subset.VertexStart + i].Pos);
                inNormals.Add(vertices[subset.VertexStart + i].Normal);
                inTangent.Add(vertices[subset.VertexStart + i].TangentU);
                inUV.Add(vertices[subset.VertexStart + i].Tex);
            }

        }
        mesh.SetVertices(inVertices);
        mesh.SetNormals(inNormals);
        mesh.SetTangents(inTangent);
        mesh.SetUVs(0, inUV);

        List<int> inIndices = new List<int>();

        for (int index = 0; index < subsets.Count; index++)
        {
            Subset subset = subsets[index];
            for (int i = 0; i < subset.FaceCount; i++)
            {
                inIndices.Add(indices[(subset.FaceStart + i) * 3]);
                inIndices.Add(indices[(subset.FaceStart + i) * 3 + 1]);
                inIndices.Add(indices[(subset.FaceStart + i) * 3 + 2]);
            }
            mesh.SetTriangles(inIndices, index);
        }

        meshFilter.sharedMesh = mesh;

    }

    private void LoadM3d(string fileName)
    {
        int numMaterials = 0;
        int numVertices = 0;
        int numTriangles = 0;
        int numBones = 0;
        int numAnimationClips = 0;

        StreamReader sr = new StreamReader(Application.dataPath + "/" + fileName, Encoding.Default);
        string content;

        content = sr.ReadLine();
        numMaterials = int.Parse(sr.ReadLine().Split(' ')[1]);
        numVertices = int.Parse(sr.ReadLine().Split(' ')[1]);
        numTriangles = int.Parse(sr.ReadLine().Split(' ')[1]);
        numBones = int.Parse(sr.ReadLine().Split(' ')[1]);
        numAnimationClips = int.Parse(sr.ReadLine().Split(' ')[1]);
        content = sr.ReadLine();

        content = sr.ReadLine();
        mats = new List<Material>(new Material[numMaterials]);
        for (int i = 0; i < numMaterials; i++)
        {
            content = sr.ReadLine();
            content = sr.ReadLine();
            content = sr.ReadLine();
            content = sr.ReadLine();
            content = sr.ReadLine();
            content = sr.ReadLine();
            content = sr.ReadLine();

            string texMapName = sr.ReadLine().Split(' ')[1];
            string noarmalMapName = sr.ReadLine().Split(' ')[1];
            content = sr.ReadLine();

            Material mat = new Material(Shader.Find("Custom/m3dAnimation"));
            Texture tex = Resources.Load<Texture>("Textures/" + texMapName.Remove(texMapName.LastIndexOf(".")));
            Texture bump = Resources.Load<Texture>("Textures/" + noarmalMapName.Remove(noarmalMapName.LastIndexOf(".")));
            mat.SetTexture("_MainTex", tex);
            mat.SetTexture("_Bump", bump);
            mats[i] = mat;
        }

        content = sr.ReadLine();
        subsets = new List<Subset>(new Subset[numMaterials]);
        for (int i = 0; i < numMaterials; i++)
        {
            string[] tempStr = sr.ReadLine().Split(' ');
            Subset subset = new Subset();
            subset.Id = int.Parse(tempStr[1]);
            subset.VertexStart = int.Parse(tempStr[3]);
            subset.VertexCount = int.Parse(tempStr[5]);
            subset.FaceStart = int.Parse(tempStr[7]);
            subset.FaceCount = int.Parse(tempStr[9]);
            subsets[i] = subset;
        }
        content = sr.ReadLine();

        content = sr.ReadLine();
        vertices = new List<PosNormalTexTanSkinned>(new PosNormalTexTanSkinned[numVertices]);
        for (int i = 0; i < numVertices; i++)
        {
            string[] tempStr = sr.ReadLine().Split(' ');
            PosNormalTexTanSkinned vertex = new PosNormalTexTanSkinned();
            vertex.Pos = new Vector3(float.Parse(tempStr[1]), float.Parse(tempStr[2]), float.Parse(tempStr[3]));

            tempStr = sr.ReadLine().Split(' ');
            vertex.TangentU = new Vector4(float.Parse(tempStr[1]), float.Parse(tempStr[2]), float.Parse(tempStr[3]), float.Parse(tempStr[4]));

            tempStr = sr.ReadLine().Split(' ');
            vertex.Normal = new Vector3(float.Parse(tempStr[1]), float.Parse(tempStr[2]), float.Parse(tempStr[3]));

            tempStr = sr.ReadLine().Split(' ');
            vertex.Tex = new Vector2(float.Parse(tempStr[1]), float.Parse(tempStr[2]));

            tempStr = sr.ReadLine().Split(' ');
            vertex.Weights = new Vector3(float.Parse(tempStr[1]), float.Parse(tempStr[2]), float.Parse(tempStr[3]));

            tempStr = sr.ReadLine().Split(' ');
            vertex.BoneIndices = new byte[4] { byte.Parse(tempStr[1]), byte.Parse(tempStr[2]), byte.Parse(tempStr[3]), byte.Parse(tempStr[4]) };
            vertices[i] = vertex;
            content = sr.ReadLine();
        }

        content = sr.ReadLine();
        indices = new List<int>(new int[numTriangles * 3]);
        for (int i = 0; i < numTriangles; i++)
        {
            string[] tempStr = sr.ReadLine().Split(' ');

            indices[i * 3 + 2] = int.Parse(tempStr[0]);
            indices[i * 3 + 1] = int.Parse(tempStr[1]);
            indices[i * 3] = int.Parse(tempStr[2]);
        }
        content = sr.ReadLine();

        content = sr.ReadLine();
        List<Matrix4x4> matrixs = new List<Matrix4x4>(new Matrix4x4[numBones]);
        for (int i = 0; i < numBones; i++)
        {
            string[] tempStr = sr.ReadLine().Split(' ');
            Matrix4x4 m = new Matrix4x4();
            m.m00 = float.Parse(tempStr[1]);
            m.m01 = float.Parse(tempStr[2]);
            m.m02 = float.Parse(tempStr[3]);
            m.m03 = float.Parse(tempStr[4]);

            m.m10 = float.Parse(tempStr[5]);
            m.m11 = float.Parse(tempStr[6]);
            m.m12 = float.Parse(tempStr[7]);
            m.m13 = float.Parse(tempStr[8]);

            m.m20 = float.Parse(tempStr[9]);
            m.m21 = float.Parse(tempStr[10]);
            m.m22 = float.Parse(tempStr[11]);
            m.m23 = float.Parse(tempStr[12]);

            m.m30 = float.Parse(tempStr[13]);
            m.m31 = float.Parse(tempStr[14]);
            m.m32 = float.Parse(tempStr[15]);
            m.m33 = float.Parse(tempStr[16]);

            matrixs[i] = m.transpose;
        }
        content = sr.ReadLine();

        content = sr.ReadLine();
        List<int> boneIndexToParentIndex = new List<int>(new int[numBones]);
        for (int i = 0; i < numBones; i++)
        {
            string[] tempStr = sr.ReadLine().Split(' ');
            boneIndexToParentIndex[i] = int.Parse(tempStr[1]);
        }
        sr.ReadLine();

        sr.ReadLine();
        Dictionary<string, xinhua302.AnimationClip> animations = new Dictionary<string, xinhua302.AnimationClip>();
        for (int i = 0; i < numAnimationClips; i++)
        {
            string clipName = sr.ReadLine().Split(' ')[1];
            content = sr.ReadLine();

            xinhua302.AnimationClip animationClip = new xinhua302.AnimationClip();
            animationClip.BoneAnimations = new List<BoneAnimation>(new BoneAnimation[numBones]);
            for (int j = 0; j < numBones; j++)
            {
                content = sr.ReadLine();
                int framesCount = int.Parse(content.Split(' ')[2]);
                sr.ReadLine();

                animationClip.BoneAnimations[j] = new BoneAnimation();
                BoneAnimation boneAnimation = animationClip.BoneAnimations[j];
                boneAnimation.Keyframes = new List<xinhua302.Keyframe>();
                for (int k = 0; k < framesCount; k++)
                {
                    string[] tempStr = sr.ReadLine().Split(' ');

                    xinhua302.Keyframe keyframe = new xinhua302.Keyframe();
                    keyframe.TimePos = float.Parse(tempStr[1]);
                    keyframe.Translation = new Vector3(float.Parse(tempStr[3]), float.Parse(tempStr[4]), float.Parse(tempStr[5]));
                    keyframe.Scale = new Vector3(float.Parse(tempStr[7]), float.Parse(tempStr[8]), float.Parse(tempStr[9]));
                    keyframe.RotationQuat = new Quaternion(float.Parse(tempStr[11]), float.Parse(tempStr[12]), float.Parse(tempStr[13]), float.Parse(tempStr[14]));

                    boneAnimation.Keyframes.Add(keyframe);
                }


                sr.ReadLine();
                sr.ReadLine();
            }

            animations[clipName] = animationClip;
        }

        skinnedData = new SkinnedData();
        skinnedData.Set(boneIndexToParentIndex, matrixs, animations);
    }
}

namespace xinhua302
{
    public struct Subset
    {
        public int Id;
        public int VertexStart;
        public int VertexCount;
        public int FaceStart;
        public int FaceCount;
    }

    public class PosNormalTexTanSkinned
    {
        public Vector3 Pos;
        public Vector3 Normal;
        public Vector2 Tex;
        public Vector4 TangentU;
        public Vector3 Weights;
        public byte[] BoneIndices = new byte[4];
    }

    public class Keyframe
    {
        public float TimePos;
        public Vector3 Translation;
        public Vector3 Scale;
        public Quaternion RotationQuat;
    };

    public class BoneAnimation
    {
        public float GetStartTime()
        {
            return Keyframes[0].TimePos;
        }
        public float GetEndTime()
        {
            return Keyframes[Keyframes.Count - 1].TimePos;
        }

        public Matrix4x4 Interpolate(float t)
        {
            Matrix4x4 m = new Matrix4x4();
            if (t <= GetStartTime())
            {
                Keyframe k = Keyframes[0];
                m = Matrix4x4.identity;
                m = Matrix4x4.Scale(k.Scale) * Matrix4x4.Translate(k.Translation) * Matrix4x4.Rotate(k.RotationQuat) * m;
            }
            else if (t >= GetEndTime())
            {
                Keyframe k = Keyframes[Keyframes.Count - 1];
                m = Matrix4x4.identity;
                m = Matrix4x4.Scale(k.Scale) * Matrix4x4.Translate(k.Translation) * Matrix4x4.Rotate(k.RotationQuat) * m;
            }
            else
            {
                for (int i = 0; i < Keyframes.Count - 1; ++i)
                {
                    if (t >= Keyframes[i].TimePos && t <= Keyframes[i + 1].TimePos)
                    {
                        float lerpPercent = (t - Keyframes[i].TimePos) / (Keyframes[i + 1].TimePos - Keyframes[i].TimePos);

                        Vector3 S = Vector3.Lerp(Keyframes[i].Scale, Keyframes[i + 1].Scale, lerpPercent);
                        Vector3 P = Vector3.Lerp(Keyframes[i].Translation, Keyframes[i + 1].Translation, lerpPercent);
                        Quaternion Q = Quaternion.Lerp(Keyframes[i].RotationQuat, Keyframes[i + 1].RotationQuat, lerpPercent);

                        m = Matrix4x4.identity;
                        m = Matrix4x4.Scale(S) * Matrix4x4.Translate(P) * Matrix4x4.Rotate(Q) * m;

                        break;
                    }
                }
            }

            return m;
        }

        public List<Keyframe> Keyframes;
    }

    public class AnimationClip
    {
        public float GetClipStartTime()
        {
            float t = float.MaxValue;
            foreach (var it in BoneAnimations)
            {
                t = Mathf.Min(t, it.GetStartTime());
            }
            return t;
        }

        public float GetClipEndTime()
        {
            float t = 0.0f;
            foreach (var it in BoneAnimations)
            {
                t = Mathf.Max(t, it.GetEndTime());
            }
            return t;
        }

        public List<Matrix4x4> Interpolate(float t)
        {
            List<Matrix4x4> boneTransforms = new List<Matrix4x4>();
            for (int i = 0; i < BoneAnimations.Count; i++)
            {
                boneTransforms.Add(BoneAnimations[i].Interpolate(t));
            }
            return boneTransforms;
        }

        public List<BoneAnimation> BoneAnimations;

    }

    public class SkinnedData
    {
        public int BoneCount()
        {
            return mBoneHierarchy.Count;
        }

        public float GetClipStartTime(string clipName)
        {
            return mAnimations[clipName] == null ? 0.0f : mAnimations[clipName].GetClipStartTime();
        }

        public float GetClipEndTime(string clipName)
        {
            return mAnimations[clipName] == null ? 0.0f : mAnimations[clipName].GetClipEndTime();
        }

        public void Set(List<int> boneHierarchy, List<Matrix4x4> boneOffsets, Dictionary<string, AnimationClip> animations)
        {
            this.mBoneHierarchy = boneHierarchy;
            this.mBoneOffsets = boneOffsets;
            this.mAnimations = animations;
        }

        public List<Matrix4x4> GetFinalTransforms(string clipName, float timePos)
        {
            int numBones = mBoneOffsets.Count;
            var clip = mAnimations[clipName];
            List<Matrix4x4> finalTransforms = new List<Matrix4x4>(new Matrix4x4[numBones]);

            List<Matrix4x4> toParentTransforms = clip.Interpolate(timePos);

            List<Matrix4x4> toRootTransforms = new List<Matrix4x4>(new Matrix4x4[numBones]);
            toRootTransforms[0] = toParentTransforms[0];

            for (int i = 1; i < numBones; i++)
            {
                Matrix4x4 toParent = toParentTransforms[i];
                int parentIndex = mBoneHierarchy[i];
                Matrix4x4 parentToRoot = toRootTransforms[parentIndex];
                Matrix4x4 toRoot = parentToRoot * toParent;

                toRootTransforms[i] = toRoot;
            }

            for (int i = 0; i < numBones; i++)
            {
                Matrix4x4 offset = mBoneOffsets[i];
                Matrix4x4 toRoot = toRootTransforms[i];
                finalTransforms[i] = toRoot * offset;
            }

            return finalTransforms;
        }

        List<int> mBoneHierarchy;
        List<Matrix4x4> mBoneOffsets;
        Dictionary<string, AnimationClip> mAnimations;

    }
}

